VERSION 1.0 CLASS
BEGIN
  MultiUse = -1  'True
END
Attribute VB_Name = "Coffee"
Attribute VB_GlobalNameSpace = False
Attribute VB_Creatable = True
Attribute VB_PredeclaredId = False
Attribute VB_Exposed = True
Option Explicit
Private Declare Function timeGetTime Lib "winmm.dll" () As Long

' The Coffee object represents a different style
'   of asynchronous notifications from those
'   performed by the CoffeeMonitors.  Instead
'   of periodic notifications Coffee provides
'   progress reports on a long task, and a
'   completion event.
'
' The mechanism used for these notifications
'   is to raise events.  You could also use
'   call-back methods, and in fact there are
'   advantages to doing so.  Call-backs would
'   allow a component to deal intelligently
'   with errors in the client, whereas events
'   don't return client errors.  This is
'   discussed in "When to Use Events or
'   Call-Backs for Notifications," in Books
'   Online.

' Number of iterations to perform in the dummy
'   task.
Private mlngIterations As Long

' XTimer is used to kick the long task off
'   asynchronously.
Private WithEvents mwXTimer As XTimer
Attribute mwXTimer.VB_VarHelpID = -1

Event Progress(ByVal PercentDone As Single, _
        ByRef Cancel As Boolean)
Event Complete(ByVal Canceled As Boolean)
        
' ThreadID returns the system thread ID of
' --------      the thread the object was
'   created on.
'
Public Property Get ThreadID() As Long
    ThreadID = App.ThreadID
End Property

' NumberOnThread returns the number of Coffee
' --------------    objects running on this
'   thread.  This is just the value of the
'   global data variable glngGlobalData, which
'   Coffee objects increment in their Initialize
'   events and decrement in their Terminate
'   events.
'
' If MTCoffee was compiled with Thread Per
'   Object, the only way for multiple objects
'   to share a thread (and the instance of
'   global data associated with it) is if
'   another Coffee has been created on this
'   thread by calling GetCoffeeOnSameThread.
'
' If MTCoffee was compiled with a Thread Pool
'   and the count of active objects exceeded
'   the number of threads in the pool, then
'   Coffee objects will be sharing threads.
'
Public Property Get NumberOnThread() As Long
    NumberOnThread = glngGlobalData
End Property

' StartLongTask sets things up for the long
' -------------     dummy task.  The task is
'   actually started by a code-only XTimer
'   that StartLongTask sets running.
'
Public Sub StartLongTask(ByVal Iterations As Long)
    ' This is a short circuit for testing call
    '   overhead.  See CallAnotherCoffee.
    If Iterations = 0 Then Exit Sub
    '
    ' Store the size of the dummy task.
    mlngIterations = Iterations
    '
    ' Give the timer a short
    '   interval, and set it running just
    '   before returning.
    mwXTimer.Interval = 55
    mwXTimer.Enabled = True
End Sub

' GetCoffeeOnSameThread creates a new Coffee
' ---------------------     object on the same
'   thread, simulating the effects of thread
'   pooling.  This can only be done internally,
'   as explained in "How Object Creation Works
'   in Visual Basic" in Books Online.
'
Public Function GetCoffeeOnSameThread() As Coffee
    ' All objects created using New will be on
    '   the creator's thread, even a new
    '   Coffee object.
    Set GetCoffeeOnSameThread = New Coffee
End Function

' GetCoffeeOnNewThread creates a new Coffee
' --------------------     object on a new
'   thread, by calling CreateObject to create
'   the new Coffee object.  The difference
'   between this and the internal creation
'   done by GetCoffeeOnSameThread is explained
'   in "How Object Creation Works in Visual
'   Basic" in Books Online.
'
' Note that this technique could be used to
'   create objects on different threads that
'   could communicate with each other, without
'   the client having to pass one object a
'   reference to the other (as CoffeeWatch
'   does).  If you experiment with this,
'   remember that the overhead of marshaling
'   calls between threads is almost as great
'   as the overhead of marshaling calls
'   across processes.
'
Public Function GetCoffeeOnNewThread() As Coffee
    ' Create as if by external client.
    Set GetCoffeeOnNewThread = CreateObject("MTCoffee.Coffee")
End Function

' CallAnotherCoffee gives a rough measure of
' -----------------     cross-thread call
'   overhead.  Pass it a Coffee object on
'   another thread, or on the same thread,
'   and compare the results; the method
'   makes dummy calls to StartLongTask, so
'   that it's essentially measuring only
'   the call overhead.
'
Public Function CallAnotherCoffee(ByVal cfe As Coffee) As Double
    Const TRIES = 10000
    Dim timeStart As Long
    Dim timeEnd As Long
    Dim lngTries As Long
    
    timeStart = timeGetTime
    For lngTries = 1 To TRIES
        cfe.StartLongTask 0
    Next
    timeEnd = timeGetTime
    '
    ' Return seconds (ss.mmm) per call.  (This
    '   will give an incorrect result if you
    '   happen to run CallAnotherCoffee just
    '   as the system timer is rolling over
    '   to zero.)
    CallAnotherCoffee = ((CDbl(timeEnd) - timeStart) / 1000#) / TRIES
End Function

Private Sub Class_Initialize()
    ' Increment the global count (that is,
    '   for this thread) of Coffee objects.
    glngGlobalData = glngGlobalData + 1
    '
    ' Create a timer object.
    Set mwXTimer = New XTimer
End Sub

Private Sub Class_Terminate()
    ' Decrement the global count (that is,
    '   for this thread) of Coffee objects.
    glngGlobalData = glngGlobalData - 1
    '
    ' Free the timer object.
    Set mwXTimer = Nothing
End Sub

Private Sub mwXTimer_Tick()
    ' First thing, turn off the timer.
    mwXTimer.Enabled = False
    Call LongTask
End Sub

' The dummy task.
'
Private Sub LongTask()
    Dim dblDummy As Double
    Dim lngCt As Long
    Dim sngNextMark As Single
    Dim blnCancel As Boolean
    
    ' For small transactions, don't bother to
    '   call back while running.
    If mlngIterations < 100000 Then
        sngNextMark = 1!
    Else
        sngNextMark = 0.1!
    End If
        
    ' This is just a time-waster.
    For lngCt = 1 To mlngIterations
        ' If this were a real application, a
        '   unit of work would be done here.
        '   You may find it interesting to
        '   replace this processor-intensive
        '   activity with one that waits on
        '   the system a lot, such as calls
        '   to a database on another machine,
        '   or reading a very large file.
        '   Throughput on a single-processor
        '   workstation is far greater when
        '   most threads are blocked,
        '   waiting for file input or the
        '   result of a database call.
        '
        dblDummy = 3033.14159 * 2081.14159 * 1138.14159
        '
        If CDbl(lngCt) / mlngIterations > sngNextMark Then
            RaiseEvent Progress(sngNextMark, blnCancel)
            If blnCancel Then
                ' If the client is tired of waiting
                '   and wants the task canceled,
                '   raise the Complete event with
                '   True (canceled).
                RaiseEvent Complete(True)
                Exit Sub
            End If
            sngNextMark = sngNextMark + 0.1!
        End If
    Next
    ' On successful completion, raise the
    '   Complete event with False (not
    '   canceled).
    RaiseEvent Complete(False)
End Sub
